package com.nuaa.shr.pls.algorithm.abst;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.uma.jmetal.algorithm.multiobjective.moead.util.MOEADUtils;
import org.uma.jmetal.problem.Problem;
import org.uma.jmetal.solution.GridPermutationSolution;
import org.uma.jmetal.util.SolutionListUtils;
import org.uma.jmetal.util.fileoutput.SolutionListOutput;
import org.uma.jmetal.util.fileoutput.impl.DefaultFileOutputContext;

import com.nuaa.shr.pls.utils.AddToEPCallable;
import com.nuaa.shr.pls.utils.GridComparator;


@SuppressWarnings("serial")
public abstract class GrWeakDominateParetoLocalSearch extends AbstractParetoLocalSearch<GridPermutationSolution<Integer>>{

	private int division;
	private double[] unitLen;	
	
	private double[] lb;
	private double[] ub;
	protected int iteration;
	
	int nThread = 20;
	ExecutorService executorsService ;
	
	public GrWeakDominateParetoLocalSearch(Problem<GridPermutationSolution<Integer>> problem, 
			int maxIteration, int populationSize, int division) {
		super(problem, maxIteration, populationSize);
		this.division = division;
		unitLen = new double[problem.getNumberOfObjectives()];
		lb = new double[problem.getNumberOfObjectives()];
		ub = new double[problem.getNumberOfObjectives()];
	}

	@Override
	public void run() {
		
		executorsService = Executors.newFixedThreadPool(nThread);
		
		List<GridPermutationSolution<Integer>> initPopulation = createInitialPopulation();
		evaluator.evaluate(initPopulation, problem);
		workPopulation = SolutionListUtils.getNondominatedSolutions(initPopulation);
		externalPopulation.addAll(workPopulation);
		new SolutionListOutput(workPopulation).setSeparator("\t")
			.setFunFileOutputContext(new DefaultFileOutputContext("results/temp/initialize.tsv")).print();
		updateSomething();
		initializeIdealPoint();
		initialNadirPoint();
		updateGridEnvironment();
		//===========高维添加此处=================
//		reduceSameGridSolution(externalPopulation);
//		reduceSameGridSolution(workPopulation);
		//**************************************************
		iteration =  0;
		while(iteration++ < maxIteration && workPopulation.size()>0){
			List<GridPermutationSolution<Integer>> tempList = new ArrayList<>();
			int[] permutation = new int[workPopulation.size()];
			MOEADUtils.randomPermutation(permutation, workPopulation.size());
			for(int i = 0;i<permutation.length;i++){
				searchNeighborhood(workPopulation.get(permutation[i]),tempList);
			}
			workPopulation.clear();
			workPopulation.addAll(tempList);
			updateIdealPoint(externalPopulation);
			if(iteration % 10 == 0)
				initialNadirPoint();
			updateGridEnvironment();
			reduceSameGridSolution(externalPopulation);
			reduceSameGridSolution(workPopulation);
			updateSomething();
			System.out.println(iteration+" iteration...");
			System.out.println("externalPopulation:"+externalPopulation.size()+
					"|| workPopulation:"+workPopulation.size()+"\n");
			/*		
			System.out.print("nadir point:");
			for(int i = 0;i<nadirPoint.length;i++){
				System.out.print(nadirPoint[i]+",");
			}
			System.out.println();
			System.out.print("ideal point:");
			for(int i = 0;i<idealPoint.length;i++){
				System.out.print(idealPoint[i]+",");
			}
			System.out.println();
			*/
		}
		executorsService.shutdown();
	}

	protected abstract void updateSomething();

	/**
	 * 去除在同一个格子里的解
	 * @param population
	 */
	private void reduceSameGridSolution(List<GridPermutationSolution<Integer>> population) {
		for(int i = 0;i<population.size();i++){
			for(int j = population.size()-1;j>i;j--){
				if(isEqualByGrid(population.get(i), population.get(j))){
//					replaceByPBI(population.get(j), i, population);
					replaceByDistance(population.get(j), i, population,1.0);
					population.remove(j);
				}
			}
		}
	}

	public boolean replaceByDistance(GridPermutationSolution<Integer> gridPermutationSolution, int i,
			List<GridPermutationSolution<Integer>> population, double norm) {
		GridPermutationSolution<Integer> solution = population.get(i);
		double solutionDistance  = 0.0;
		double neighborSolutionDistance = 0.0;
//		double sum = 0.0;
//		for(int k = 0;k<solution.getNumberOfObjectives();k++){
//			sum += solution.getObjective(k);
//		}
		for(int index = 0;index<solution.getNumberOfObjectives();index++){
			int coordinate = solution.getGridCoordinate(index);
			double coef = 1.0 / ((double) coordinate + 0.0000001);
//			double coef = 1.0;
			
			double gridIdealValue = lb[index] + coordinate * unitLen[index];
			solutionDistance += Math.pow((solution.getObjective(index)-gridIdealValue)*coef, norm);
			neighborSolutionDistance += Math.pow((gridPermutationSolution.getObjective(index)-gridIdealValue)*coef, norm);
		}
		solutionDistance = Math.pow(solutionDistance, 1.0/norm);
		neighborSolutionDistance = Math.pow(neighborSolutionDistance, 1.0/norm);
		if(neighborSolutionDistance < solutionDistance){
			population.set(i, gridPermutationSolution);
			return true;
		}else return false;
		
	}

	public boolean replaceByPBI(GridPermutationSolution<Integer> gridPermutationSolution, int i,
			List<GridPermutationSolution<Integer>> population) {
		GridPermutationSolution<Integer> solution = population.get(i);
		double[] solutionDiff  = new double[problem.getNumberOfObjectives()];
		double[] neighborSolutionDiff= new double[problem.getNumberOfObjectives()];
		
		double[] coordinates = new double[solution.getNumberOfObjectives()];
		double sum = 0.0;
		for(int index = 0;index<solution.getNumberOfObjectives();index++){
			int coordinate = solution.getGridCoordinate(index);
			
			coordinates[index] = coordinate;
			sum += coordinate;
			
			double gridIdealValue = coordinate * unitLen[index];
			solutionDiff[index] = solution.getObjective(index)-gridIdealValue;
			neighborSolutionDiff[index] = gridPermutationSolution.getObjective(index)-gridIdealValue;
		}
		
		for(int k = 0;k<coordinates.length;k++){
			coordinates[k] = coordinates[k] / sum;
		}
		
		if(pbi(solutionDiff, coordinates) > pbi(neighborSolutionDiff, coordinates)){
			population.set(i, gridPermutationSolution);
			return true;
		}else return false;
		
	}

	protected abstract void searchNeighborhood(GridPermutationSolution<Integer> solution, List<GridPermutationSolution<Integer>> tempList);

	protected boolean addSolutionToExternal(GridPermutationSolution<Integer> neighborSolution) {
		int extSize = externalPopulation.size();
		List<Future<Boolean>> futures = new CopyOnWriteArrayList<>();
		List<GridPermutationSolution<Integer>> sumList = new CopyOnWriteArrayList<>();
		int nTh = extSize / 20 + 1;
		for (int i = 0; i < nTh; i++) {
			List<GridPermutationSolution<Integer>> subList = new CopyOnWriteArrayList<>();
			if(i != nTh-1)
				subList.addAll(externalPopulation.subList(20 * i, 20 * (i + 1)));
			else subList.addAll(externalPopulation.subList(20 * i, externalPopulation.size()));
			
			Callable<Boolean> task = new AddToEPCallable(subList, neighborSolution, unitLen, lb);
			futures.add(executorsService.submit(task));
			sumList.addAll(subList);
		}
		externalPopulation.clear();
		externalPopulation.addAll(sumList);
		boolean flag = true;
		try {
			for (Future<Boolean> future : futures) {
				if (!future.get()) {
					flag = false;
					break;
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.exit(0);
		} catch (ExecutionException e) {
			e.printStackTrace();
			System.exit(0);
		}
		if (flag) {
			externalPopulation.add(neighborSolution);
			return true;
		} else
			return false;
	}

//	private boolean addToExternalAccordingToGrid(GridPermutationSolution<Integer> neighborSolution) {
//		for(int i = 0;i<externalPopulation.size();i++){
//			GridPermutationSolution<Integer> solution = externalPopulation.get(i);
//			if(isEqualByGrid(solution, neighborSolution)){
//				if(replaceByDistance(neighborSolution,i,externalPopulation,1.0))
////				if(replaceByPBI(neighborSolution,i,externalPopulation))
//					return true;
//				else return false;
//			}
//		}
//		externalPopulation.add(neighborSolution);
//		return true;
//	}


	private double pbi(double[] solutionDiff, double[] vector) {
	      double d1, d2, nl;
	      double theta = 5.0;
	      
	      d1 = d2 = nl = 0.0;
	      
	      for (int i = 0; i < problem.getNumberOfObjectives(); i++) {
	        d1 += solutionDiff[i] * vector[i];
	        nl += Math.pow(vector[i], 2.0);
	      }
	      nl = Math.sqrt(nl);
	      d1 = Math.abs(d1) / nl;

	      for (int i = 0; i < problem.getNumberOfObjectives(); i++) {
	        d2 += Math.pow( solutionDiff[i] - d1 * (vector[i] / nl), 2.0);
	      }
	      d2 = Math.sqrt(d2);

	      return (d1 + theta * d2);
	}

	private boolean isEqualByGrid(GridPermutationSolution<Integer> solution,
			GridPermutationSolution<Integer> neighborSolution) {
		for(int i = 0;i<solution.getNumberOfObjectives();i++){
			if(solution.getGridCoordinate(i) != neighborSolution.getGridCoordinate(i))
				return false;
		}
		return true;
	}

//	private boolean isOutOfBounds(GridPermutationSolution<Integer> neighborSolution) {
//		for(int i = 0;i<neighborSolution.getNumberOfObjectives();i++){
//			if(neighborSolution.getObjective(i) > ub[i] || 
//					neighborSolution.getObjective(i)<lb[i]){
//				return true;
//			}
//		}
//		return false;
//	}

	/**
	 * 添加到临时population中，临时population的解将在循环的末尾赋值给workPopulation
	 * @param neighborSolution
	 * @param tempList
	 * @return
	 */
	protected boolean addToTempPopulation(GridPermutationSolution<Integer> neighborSolution,
			List<GridPermutationSolution<Integer>> tempList) {
		if (!plsUtils.isEqualsToList(neighborSolution, workPopulation)) {
			for(int i = tempList.size()-1; i>=0; i--){
				GridPermutationSolution<Integer> solution = tempList.get(i);
	 			GridComparator comparator = new GridComparator();
	 			int tempFlag = comparator.compare(neighborSolution,solution);
	 			if(tempFlag==-1||isEqualByGrid(solution, neighborSolution)){
	 				tempList.remove(i);
	 			}
			}
			tempList.add(neighborSolution);
			return true;
		}else return false;
	}

	private void updateGridEnvironment() {
		for(int i = 0;i<unitLen.length;i++){
			
			double sigma = (nadirPoint[i] - idealPoint[i])/(double) (2.0*division);
			ub[i] = nadirPoint[i]+sigma;
			lb[i] = idealPoint[i]-sigma;
			unitLen[i] = ( ub[i]-lb[i] ) / (double) division;
		}
		for(GridPermutationSolution<?> solution:externalPopulation){
			setGridCoordinate(solution);
		}
		for(GridPermutationSolution<?> solution:workPopulation){
			setGridCoordinate(solution);
		}
	}

	protected void setGridCoordinate(GridPermutationSolution<?> solution) {
		for(int i = 0;i<solution.getNumberOfObjectives();i++){
			/**
			 * 如果范围小于idealpoint，坐标为负值
			 */
			int gridCoordinate = (int) ((solution.getObjective(i) - lb[i])/unitLen[i]);
			solution.setGridCoordinate(i, gridCoordinate);
		}
	}


	
	@Override
	public String getName() {
		return "cdg-pls";
	}

	@Override
	public String getDescription() {
		return "cdg Pareto Local Search";
	}
	
	@Override
	public List<GridPermutationSolution<Integer>> getResult() {
		return SolutionListUtils.getNondominatedSolutions(externalPopulation);
	}

}
